package bd403.Backend;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.TreeMap;

import bd403.namespace.R;

import android.app.Application;

 /* Application class for BrewDawg. Manages drinks for the application and saves
  * state throughout the life of the application.
  * Main author: Mike & Justin
  */
public class DrinkStorage extends Application {
	private final int N_TOP_DRINKS = 5;
	private final int MIN_NUM_RATINGS = 3;
	protected DrinkDB dbConn;
	protected Map<String, Drink> drinkMap; // map from Drink Name -> Drink
	private User currentUser; // user of the application
	private Drink currentDrink; // for data binding a Drink in-between Activities
	private int version; // keeps track of the state of the application to sync with the database

	public DrinkStorage() {
		super();
		this.dbConn = DrinkDB.getInstance();
		this.currentUser = null;
		this.currentDrink = null;
		  updateDrinkStorageFromDB();
	}
	
	/* Returns a String representing the user name of the current user of the application.
	 */
	public String getUsername() {
		return currentUser.getUsername();
	}
	
	
	/* Returns an iterator containing all the drinks
	 * in the current storage.
	 */
	public Iterator<Drink> allDrinks() {
		if (version != 1) {
			updateDrinkStorageFromDB();
			this.version = 1;
		}
		return drinkMap.values().iterator();
	}
	
	/*
	 * Returns a Drink d where d.getName().equals(query). If no drink exists
	 * with a name equal to query, returns null.
	 */
	public Drink getDrinkByName(String query) {
		for (Drink d : drinkMap.values()) {
			if (d.getName().toLowerCase().equals(query.toLowerCase())) {
				return d;
			}
		}
		return null;
	}
	
	/* Returns an iterator containing all drinks whose name contains
	 * the query String. Returns null if drinkMap is empty. 
	 */
	public Iterator<Drink> findMatchingDrinks(String query) {
		if (version != 1) {
			updateDrinkStorageFromDB();
			this.version = 1;
		}
		
		List<Drink> results = new ArrayList<Drink>();
		for (Drink d : drinkMap.values()) {
			if (d.getName().toLowerCase().contains(query.toLowerCase())) {
				results.add(d);
			}
		}
		if (results.isEmpty()) {
			return null;
		} else {
			return results.iterator();
		}
	}

	/* Accepts a DrinkParams object that describes the drink to add. Returns
	 * true if a new drink is successfully added to the application. Otherwise
	 * returns false.
	 */
	public boolean addDrink(DrinkParams dp) {
		if (dbConn.addDrink(dp, getUsername())) {
			updateDrinks(dp);
			this.version = -1;
			return true;
		} else {
			return false;
		}
	}
	
	/* Returns a List of all the drink names for drinks in this application. 
	 */
	public List<String> getDrinkNames() {
		List<String> drinkNameList = new ArrayList<String>();
		for (Drink d : drinkMap.values()) {
			drinkNameList.add(d.getName());
		}
		return drinkNameList;
	}
	
	/* Returns an iterator containing the top (N_TOP_DRINKS) drinks for this 
	 * application. Drinks are compared against one another based on average 
	 * rating.
	 */
	public Iterator<Drink> getTopDrinks() {
		if (version != 1) {
			updateDrinkStorageFromDB();
			this.version = 1;
		}
		
		List<Drink> allDrinks = new ArrayList<Drink>(drinkMap.values());
		Collections.sort(allDrinks, new DrinkComparator());
		if (allDrinks.size() <= N_TOP_DRINKS) {
			return allDrinks.iterator();
		} else {
			List<Drink> topDrinks = new ArrayList<Drink>();
			for (Drink current : allDrinks) {
				if (current.getNumRatings() >= MIN_NUM_RATINGS) {
					topDrinks.add(current);
				}
				
				// Return if we have found enough top drinks
				if (topDrinks.size() >= N_TOP_DRINKS) {
					return topDrinks.iterator();
				}
			}
			// Return all the top drinks we found
			return topDrinks.iterator();
		}
	}
	
	/* Returns an Iterator containing the favorite drinks for the current user
	 */
	public Iterator<Drink> getFavDrinks() {
		if (version != 1) {
			updateDrinkStorageFromDB();
			this.version = 1;
		}
		return currentUser.getFavorites();
	}
	
	/*
	 * Adds a Review to the given drink, both locally and in the DB
	 */
	public boolean addReview(DrinkParams dp) {
		if(dp == null || userReviewExists(dp.getName())) {
			return false;
		}
		Drink updateDrink = drinkMap.get(dp.getName());
		if (updateDrink == null) {
			return false;
		}
		
		// Try to add the review to the database. If it succeeds, also update
		// the local version.
		if (dbConn.addReview(dp, getUsername())) {
			String comment = dp.getComment();
			int rating = dp.getRating();

			Review r = new Review(rating, comment, getUsername());
			updateDrink.addRating(r);
			version = -1;
			
			// Success!
			return true;
		} else {
			return false;
		}
	}
	
	/* Accepts a String representing the name of a drink as parameter. Returns
	 * true if the current user has already reviewed a drink with name equal
	 * to drinkName. Otherwise returns false.
	 */
	public boolean userReviewExists(String drinkName) {
		if (drinkMap.containsKey(drinkName)) {
			Drink d = drinkMap.get(drinkName);
			ListIterator<Review> reviews = d.getReviews();
			while (reviews.hasPrevious()) {
				Review r = reviews.previous();
				if (r.getUsername().equals(getUsername())) {
					return true;
				}
			}
		}
		return false;
	}
	
	/* Accepts a Drink d as parameter and adds this drink to the favorite drinks
	 * of the current user.
	 */
	public boolean addUserFavorite(Drink d) {
		if(d == null)
			throw new IllegalArgumentException(getString(R.string.toast_addfav_null));
		if (!currentUser.favoriteExists(d.getName())) {
			if (dbConn.addFavorite(getUsername(), d.getName())) {
				currentUser.addToFavorites(d);
				return true;
			} else {
				return false;
			}
		} else {
			throw new IllegalStateException(getString(R.string.toast_addfav_already));
		}
	}

	/*
	 * Data bind Drink d to pass between activities.
	 */
	public void setCurrentDrink(Drink d) {
		currentDrink = d;
	}
	
	/*
	 * Returns the current data bound Drink.
	 */
	public Drink getCurrentDrink() {
		return currentDrink;
	}
	
	/* Accepts a String username as parameter and initializes
	 * the currentUser after a successful login.
	 */
	public void initializeUser(String username) {
		currentUser = new User(username);
		Iterator<String> favDrinkNames = dbConn.getFavorite(username);
		if (favDrinkNames != null) {
			while (favDrinkNames.hasNext()) {
				String currDrink = favDrinkNames.next();
				if (drinkMap.containsKey(currDrink)) {
					currentUser.addToFavorites(drinkMap.get(currDrink));
				}
			}
		}
	}
	
	/* Pulls drinks from the database and initializes the local drink storage.
	 */
	private void updateDrinkStorageFromDB() {
		Iterator<Drink> dbIterator = dbConn.getDrinksFromDB();
		drinkMap = new TreeMap<String, Drink>();
		if (dbIterator != null) {
			while(dbIterator.hasNext()) {
				Drink newDrink = dbIterator.next();
				drinkMap.put(newDrink.getName(), newDrink);
			}
		}
		this.version = 1;
	}
	
	/* Accepts a DrinkParams object that describes the drink to add. Adds
	 * a new drink to the local storage of drinks.
	 */
	private void updateDrinks(DrinkParams dp) {
		Drink newDrink = null;
		if (dp.getType().equals("Beer")) {
			newDrink = new Beer(dp.getName());
		} else if (dp.getType().equals("Wine")) {
			newDrink = new Wine(dp.getName());
		} else if (dp.getType().equals("Mixed")) {
			newDrink = new Mixed(dp.getName());
		}
		
		if (newDrink != null) {
			Review r = new Review(dp.getRating(), dp.getComment(), getUsername());
			newDrink.addRating(r);
			drinkMap.put(newDrink.getName(), newDrink);
		}
	}
	
	/* Comparator used to compare drinks. Drinks are compared
	 * based on average rating.
	 */
	private class DrinkComparator implements Comparator<Drink>{
	    public int compare(Drink d1, Drink d2) {
	    	if (d1.getRating() > d2.getRating()) {
	    		return -1;
	    	} else if (d1.getRating() < d2.getRating()) {
	    		return 1;
	    	} else {
	    		return 0;
	    	}
	    }
	}

}